/*
  This file is part of 'Trivial chess'.

  'Trivial chess' is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  'Trivial chess' is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with 'Trivial chess'.  If not, see <http://www.gnu.org/licenses/>.
*/

/* SVN FILE: $Id$ */
/**
 * @author         $Author$
 * @version        $Rev$
 * @lastrevision   $Date$
 * @filesource     $URL$
 */

#ifndef MOVEMAKERSTD_H_
#define MOVEMAKERSTD_H_

#include "MoveMakerAbstract.h"

template<class B, class Mv, class UI>
class MoveMakerStd
	: public MoveMakerAbstract<MoveMakerStd,B,Mv,UI> {
public:
	typedef UI UnmakeMoveInfo;
public:
	static void make(const Move move, UnmakeMoveInfo& info, Board& board) {

		const Man man = move.man(board);
		const Color sideToMove = board.sideToMove();
		const Man capturedMan = move.capturedMan(board);
		const bool isCapture = !capturedMan.isNil();
		const bool isEnpassant = move.isEnpassant(board);
		const bool isPromotion = move.isPromotion();
		const bool isQueenCastling = move.isQueenCastling(board);
		const bool isKingCastling = move.isKingCastling(board);
		const bool isPawnDoubleAdvance = move.isPawnDoubleAdvance(board);

		// Store informations for move unmake
		info.setMove(move);
		info.setCapturedMan(move.capturedMan(board));
		info.setCastlingAvailability(board.castlingAvailability());
		info.setEnpassant(board.enpassant());
		info.setHalfmoveClock(board.halfmoveClock());

		// unset departure square
		board.unset(move.from());

		if ( isKingCastling )
			board.unset(Square(File::H(), Rank::first(sideToMove)));
		else if ( isQueenCastling )
			board.unset(Square(File::A(), Rank::first(sideToMove)));

		// remove captured pieces
		if ( isEnpassant )
			board.unset(Square(move.to().file(), move.from().rank()));
		else if ( isCapture )
			board.unset(move.to());

		// set arrival square
		if ( isPromotion )
			board.set(move.to(), Man(sideToMove, move.promoted()));
		else
			board.set(move.to(), man);

		if ( isKingCastling )
			board.set(Square(File::F(), Rank::first(sideToMove)),
						Man(sideToMove, Piece::ROOK()));
		else if ( isQueenCastling )
			board.set(Square(File::D(), Rank::first(sideToMove)),
						Man(sideToMove, Piece::ROOK()));


		// set castling availability
		if ( man.isKing() ) {
			typename Board::CastlingAvailability ca = board.castlingAvailability();
			ca.unset(sideToMove);
			board.setCastlingAvailability(ca);
		}
		else if ( man.isRook() ) {
			typename Board::CastlingAvailability ca = board.castlingAvailability();

			if ( ca.king(sideToMove)
					&& move.from() == Square(File::H(), Rank::first(sideToMove)) ) {
				ca.unsetKing(sideToMove);
				board.setCastlingAvailability(ca);
			}
			else if ( ca.queen(sideToMove)
					&& move.from() == Square(File::A(), Rank::first(sideToMove)) ) {

				ca.unsetQueen(sideToMove);
				board.setCastlingAvailability(ca);
			}
		}
		if ( capturedMan.isRook()  ) {
			const Color rivalSideToMove = sideToMove.rival();
			typename Board::CastlingAvailability ca = board.castlingAvailability();

			if ( ca.king(rivalSideToMove)
					&& move.from() == Square(File::H(), Rank::first(rivalSideToMove)) ) {
				ca.unsetKing(rivalSideToMove);
				board.setCastlingAvailability(ca);
			}
			else if ( ca.queen(rivalSideToMove)
					&& move.from() == Square(File::A(), Rank::first(rivalSideToMove)) ) {

				ca.unsetQueen(rivalSideToMove);
				board.setCastlingAvailability(ca);
			}
		}


		// enpassant square set
		if ( isPawnDoubleAdvance )
			board.setEnpassant(Square(move.from().file(),
					 board.sideToMove().isWhite() ? Rank::R3() : Rank::R6() ));
		else
			board.setEnpassant(Square::NIL());

		// halfmove clock
		if ( isCapture || man.isPawn() )
			board.resetHalfmoveClock();
		else
			board.incHalfmoveClock();

		// fullmove number
		if ( board.sideToMove().isBlack() )
			board.incFullmoveNumber();

		// switch side to move
		board.setRivalToMove();

	}


	static void unmake(const UnmakeMoveInfo& info, Board& board) {

		const Move move = info.move();
		const Man man = move.manWas(board);
		const Color sideToMove = man.color();
		const Man capturedMan = move.capturedManWas(info);
		const bool wasCapture = !capturedMan.isNil();
		const bool wasEnpassant = move.wasEnpassant(board, info);
		const bool wasQueenCastling = move.wasQueenCastling(board);
		const bool wasKingCastling = move.wasKingCastling(board);


		// switch side to move
		board.setRivalToMove();

		// fullmove number
		if ( sideToMove.isBlack() )
			board.decFullmoveNumber();

		// halfmove clock
		board.setHalfmoveClock(info.halfmoveClock());

		// enpassant square set
		board.setEnpassant(info.enpassant());

		// castling availability set
		board.setCastlingAvailability(info.castlingAvailability());

		// unset arrival square
		board.unset(move.to());

		if ( wasKingCastling )
			board.unset(Square(File::F(), Rank::first(sideToMove)));
		else if ( wasQueenCastling )
			board.unset(Square(File::D(), Rank::first(sideToMove)));

		// put back captured pieces
		if ( wasEnpassant )
			board.set(
					Square(info.enpassant().file(), move.from().rank()),
					Man(sideToMove.rival(), Piece::PAWN()));
		else if ( wasCapture )
			board.set(move.to(), capturedMan);

		// set departure square
		board.set(move.from(), man);

		if ( wasKingCastling )
			board.set(Square(File::H(), Rank::first(sideToMove)),
						Man(sideToMove, Piece::ROOK()));
		else if ( wasQueenCastling )
			board.set(Square(File::A(), Rank::first(sideToMove)),
						Man(sideToMove, Piece::ROOK()));
	}
};

#endif /* MOVEMAKERSTD_H_ */
